﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Input;
using MineSweeper.Data;
using Microsoft.WindowsAPICodePack.Dialogs;
using System.Windows.Interop;
using MineSweeper.Themes;
using SwiftMVVM.Commands;
using SwiftMVVM.ViewModel;
using System.ComponentModel.DataAnnotations;
using System.Collections;
using System.Windows.Controls;
using System.Runtime.Serialization;

namespace MineSweeper.Commands
{
    public static class GameCommands
    {
        /// <summary>
        /// Display the about box
        /// </summary>
        public static RelayCommand<DifficultyManager> ShowResetDialog
        {
            get
            {
                return new RelayCommand<DifficultyManager>(difficulty =>
                {
                    if (Environment.OSVersion.Version.Major >= 6)
                    {
                        using (var enableThemingInScope = new EnableThemingInScope(true))
                        {
                            using (var taskDialog = new TaskDialog())
                            {
                                taskDialog.Caption = "Reset Statistics";
                                taskDialog.InstructionText = "Do you want to reset all your statistics to zero?";
                                taskDialog.Cancelable = true;
                                var windowInteropHelper = new WindowInteropHelper(App.Current.MainWindow);
                                taskDialog.OwnerWindowHandle = windowInteropHelper.Handle;
                                var closeDialog = new Action<object>((s) =>
                                {
                                    ((TaskDialog)((TaskDialogButton)s).HostingDialog).Close();
                                });
                                var quit = new TaskDialogButton(
                                    "ResetButton",
                                    "Yes");
                                quit.Click += ((s, ec) =>
                                {
                                    closeDialog(s);

                                    //reset all settings to 0
                                    GameCommands.ResetStats(difficulty);
                                });

                                var restart = new TaskDialogButton(
                                    "NoButton",
                                    "No");
                                restart.Click += ((s, ec) =>
                                {
                                    closeDialog(s);
                                });

                                taskDialog.Controls.Add(quit);
                                taskDialog.Controls.Add(restart);

                                taskDialog.Show();
                            }
                        }
                    }
                    else if (MessageBox.Show("Do you want to reset all your statistics to zero?", "Reset Statistics", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
                    {
                        GameCommands.ResetStats(difficulty);
                    }
                },
                difficulty =>
                {
                    return difficulty != null;
                }
                );
            }
        }

        /// <summary>
        /// Resets the entire game
        /// </summary>
        public static RelayCommand<TileMap> NewGame
        {
            get
            {
                return new RelayCommand<TileMap>(tileMap =>
                {
                    if (Environment.OSVersion.Version.Major >= 6 && !tileMap.IsNewGame)
                    {
                        using (var enableThemingInScope = new EnableThemingInScope(true))
                        {
                            using (var taskDialog = new TaskDialog())
                            {
                                taskDialog.Caption = "New Game";
                                taskDialog.InstructionText = "What do you want to do with the game in progress?";
                                taskDialog.Cancelable = true;
                                var windowInteropHelper = new WindowInteropHelper(App.Current.MainWindow);
                                taskDialog.OwnerWindowHandle = windowInteropHelper.Handle;
                                var closeDialog = new Action<object>((s) =>
                                {
                                    ((TaskDialog)((TaskDialogCommandLink)s).HostingDialog).Close();
                                });
                                var quit = new TaskDialogCommandLink(
                                    "QuitCommandLink",
                                    "Quit and start a new game",
                                    "This counts as a loss in your statistics.");
                                quit.Click += ((s, e) =>
                                {
                                    closeDialog(s);
                                    tileMap.NewGame();
                                });

                                var restart = new TaskDialogCommandLink(
                                    "RestartCommandLink",
                                    "Restart this game",
                                    "This counts as a loss in your statistics.");
                                restart.Click += ((s, e) =>
                                {
                                    closeDialog(s);
                                    tileMap.ResetGame();
                                });

                                var keepPlaying = new TaskDialogCommandLink(
                                    "KeepPlayingCommandLink",
                                    "Keep playing");
                                keepPlaying.Click += ((s, e) => { closeDialog(s); });

                                taskDialog.Controls.Add(quit);
                                taskDialog.Controls.Add(restart);
                                taskDialog.Controls.Add(keepPlaying);

                                taskDialog.Show();
                            }
                        }
                    }
                    else
                    {
                        tileMap.NewGame();
                    }
                });
            }
        }

        /// <summary>
        /// Display the about box
        /// </summary>
        public static RelayCommand<Type> ShowWindowModal
        {
            get
            {
                return new RelayCommand<Type>((windowType) =>
                {
                    if (!windowType.IsSubclassOf(typeof(Window))) { throw new InvalidOperationException("windowType must derive from Window"); }
                    var window = (Window)Activator.CreateInstance(windowType);

                    window.Owner = App.Current.MainWindow;
                    window.ShowDialog();
                });
            }
        }

        /// <summary>
        /// Uncover the current tile, this also may have
        /// the side effect of uncovering adjacent tiles
        /// </summary>
        public static RelayCommand<Tile> UncoverTile
        {
            get
            {
                return new RelayCommand<Tile>(tile =>
                {
                    if (tile.State != TileState.Flagged && tile.IsFocused)
                    {
                        tile.Parent.UncoverTile(tile);
                        CheckForWin(tile.Parent);
                    }
                }, tile =>
                {
                    return tile != null && tile.State != TileState.Uncovered;
                });
            }
        }

        /// <summary>
        /// Focus the current tile
        /// </summary>
        public static RelayCommand<Tile> FocusTile
        {
            get
            {
                return new RelayCommand<Tile>(tile =>
                {
                    tile.Focus();
                }, tile =>
                {
                    return tile != null;
                });
            }
        }

        /// <summary>
        /// Focus the current tile
        /// </summary>
        public static RelayCommand<Tile> ClearFocusTile
        {
            get
            {
                return new RelayCommand<Tile>(tile =>
                {
                    if (tile.Parent.FocusedTile == tile)
                    {
                        tile.ClearFocus();
                    }
                }, tile =>
                {
                    return tile != null;
                });
            }
        }

        public static RelayCommand<TileMap> FocusMoveRight
        {
            get
            {
                return new RelayCommand<TileMap>(tileMap =>
                {
                    MoveTileFocus(tileMap, Dock.Right);
                }, tileMap =>
                { return tileMap != null; });
            }
        }

        public static RelayCommand<TileMap> FocusMoveLeft
        {
            get
            {
                return new RelayCommand<TileMap>(tileMap =>
                {
                    MoveTileFocus(tileMap, Dock.Left);
                }, tileMap =>
                { return tileMap != null; });
            }
        }

        public static RelayCommand<TileMap> FocusMoveUp
        {
            get
            {
                return new RelayCommand<TileMap>(tileMap =>
                {
                    MoveTileFocus(tileMap, Dock.Top);
                }, tileMap =>
                { return tileMap != null; });
            }
        }

        public static RelayCommand<TileMap> FocusMoveDown
        {
            get
            {
                return new RelayCommand<TileMap>(tileMap =>
                {
                    MoveTileFocus(tileMap, Dock.Bottom);
                }, tileMap =>
                { return tileMap != null; });
            }
        }

        private static void MoveTileFocus(TileMap tileMap, Dock direction)
        {
            if (tileMap.FocusedTile == null)
            {
                tileMap.FocusedTile = tileMap.Tiles.First();
            }
            else
            {
                var currentIndex = tileMap.AllTiles.IndexOf(tileMap.FocusedTile);
                var newIndex = currentIndex +=
                        direction == Dock.Left ? -1 :
                        direction == Dock.Right ? +1 :
                        direction == Dock.Top ? -(tileMap.Width + 2) :
                        tileMap.Width + 2;
                var potentialFocusTile = tileMap.AllTiles[newIndex];
                if (potentialFocusTile.Type != TileType.NoOp)
                {
                    potentialFocusTile.Focus();
                }
            }
        }

        /// <summary>
        /// Records the changes made in the options window and applies them to the game (Also closes the OptionsWindow)
        /// </summary>
        public static RelayCommand<object[]> SaveOptionsAndCloseWindow
        {
            get
            {
                return new RelayCommand<object[]>(items =>
                {
                    var optionData = new { Window = (Window)items[0], Options = (GameOptions)items[1] };
                    optionData.Options.Save();
                    optionData.Window.Close();
                }, items =>
                {
                    if (items != null && items.Length == 2)
                    {
                        var gameOptions = items[1] as GameOptions;
                        if (gameOptions != null)
                        {
                            return DataErrorInfoSupport.ValidateModel(gameOptions, new List<System.ComponentModel.DataAnnotations.ValidationResult>());
                        }
                    }
                    return false;
                });
            }
        }

        /// <summary>
        /// Records the appearance changes made and closes the ChangeAppearance window
        /// </summary>
        public static RelayCommand<object[]> SaveAppearanceAndCloseWindow
        {
            get
            {
                return new RelayCommand<object[]>(items =>
                {
                    var optionData = new { Window = (Window)items[0], Options = (GameOptions)items[1] };
                    optionData.Options.SaveAppearance();
                    optionData.Window.Close();
                }, items =>
                {
                    if (items != null && items.Length == 2)
                    {
                        var gameOptions = items[1] as GameOptions;
                        if (gameOptions != null)
                        {
                            return DataErrorInfoSupport.ValidateModel(gameOptions, new List<System.ComponentModel.DataAnnotations.ValidationResult>());
                        }
                    }
                    return false;
                });
            }

        }

        /// <summary>
        /// Flips the Tile marker but does not uncover the tile
        /// </summary>
        public static RelayCommand<Tile> ToggleTileMarker
        {
            get
            {
                return new RelayCommand<Tile>(tile =>
                {
                    tile.State = tile.State == TileState.Question ?
                            TileState.Covered :
                            tile.State == TileState.Covered ?
                            TileState.Flagged : Properties.Settings.Default.GameOptionAllowCheckmarks ?
                            TileState.Question : TileState.Covered;

                    CheckForWin(tile.Parent);
                }, tile =>
                {
                    return tile.State != TileState.Uncovered;
                });
            }
        }

        public static void RecordStats(bool win)
        {
            switch ((GameDifficulty)Properties.Settings.Default.GameOptionDifficultyInt)
            {
                // STATISTICS:
                // GamesWon
                // GamesPlayed
                // WinStreak
                // LossStreak
                // CurrentStreak
                // WinPercent
                // CurrentLossStreak
                // CurrentWinStreak


                // Uses game difficulty to track which stats to edit
                case GameDifficulty.Beginner:

                    Properties.Settings.Default.StatGamesPlayedBeg++;

                    if (win)
                    {
                        Properties.Settings.Default.StatGamesWonBeg++;
                        Properties.Settings.Default.StatCurrentWinStreakBeg++;
                        Properties.Settings.Default.StatCurrentStreakBeg = Properties.Settings.Default.StatCurrentWinStreakBeg;
                        Properties.Settings.Default.StatCurrentLossStreakBeg = 0;
                    }
                    else
                    {
                        Properties.Settings.Default.StatCurrentLossStreakBeg++;
                        Properties.Settings.Default.StatCurrentStreakBeg = 0;
                        Properties.Settings.Default.StatCurrentWinStreakBeg = 0;
                    }

                    //if current streak is a new record, then set
                    if (Properties.Settings.Default.StatWinStreakBeg < Properties.Settings.Default.StatCurrentWinStreakBeg)
                    {
                        Properties.Settings.Default.StatWinStreakBeg = Properties.Settings.Default.StatCurrentWinStreakBeg;
                    }
                    else if (Properties.Settings.Default.StatLossStreakBeg < Properties.Settings.Default.StatCurrentLossStreakBeg)
                    {
                        Properties.Settings.Default.StatLossStreakBeg = Properties.Settings.Default.StatCurrentLossStreakBeg;
                    }

                    if (Properties.Settings.Default.StatGamesPlayedBeg > 0)
                    {
                        Properties.Settings.Default.StatWinPercentBeg =
                            Math.Round(((decimal)Properties.Settings.Default.StatGamesWonBeg /
                            (decimal)Properties.Settings.Default.StatGamesPlayedBeg) * 100, 2);
                    }

                    break;

                case GameDifficulty.Intermediate:

                    Properties.Settings.Default.StatGamesPlayedInt++;

                    if (win)
                    {
                        Properties.Settings.Default.StatGamesWonInt++;
                        Properties.Settings.Default.StatCurrentWinStreakInt++;
                        Properties.Settings.Default.StatCurrentStreakInt = Properties.Settings.Default.StatCurrentWinStreakInt;
                        Properties.Settings.Default.StatCurrentLossStreakInt = 0;
                    }
                    else
                    {
                        Properties.Settings.Default.StatCurrentLossStreakInt++;
                        Properties.Settings.Default.StatCurrentStreakInt = 0;
                        Properties.Settings.Default.StatCurrentWinStreakInt = 0;
                    }

                    //if current streak is a new record, then set
                    if (Properties.Settings.Default.StatWinStreakInt < Properties.Settings.Default.StatCurrentWinStreakInt)
                    {
                        Properties.Settings.Default.StatWinStreakInt = Properties.Settings.Default.StatCurrentWinStreakInt;
                    }
                    else if (Properties.Settings.Default.StatLossStreakInt < Properties.Settings.Default.StatCurrentLossStreakInt)
                    {
                        Properties.Settings.Default.StatLossStreakInt = Properties.Settings.Default.StatCurrentLossStreakInt;
                    }

                    if (Properties.Settings.Default.StatGamesPlayedInt > 0)
                    {
                        Properties.Settings.Default.StatWinPercentInt =
                            Math.Round(((decimal)Properties.Settings.Default.StatGamesWonInt /
                            (decimal)Properties.Settings.Default.StatGamesPlayedInt) * 100, 2);
                    }

                    break;

                case GameDifficulty.Advanced:

                    Properties.Settings.Default.StatGamesPlayedAdv++;

                    if (win)
                    {
                        Properties.Settings.Default.StatGamesWonAdv++;
                        Properties.Settings.Default.StatCurrentWinStreakAdv++;
                        Properties.Settings.Default.StatCurrentStreakAdv = Properties.Settings.Default.StatCurrentWinStreakAdv;
                        Properties.Settings.Default.StatCurrentLossStreakAdv = 0;
                    }
                    else
                    {
                        Properties.Settings.Default.StatCurrentLossStreakAdv++;
                        Properties.Settings.Default.StatCurrentStreakAdv = 0;
                        Properties.Settings.Default.StatCurrentWinStreakAdv = 0;
                    }

                    //if current streak is a new record, then set
                    if (Properties.Settings.Default.StatWinStreakAdv < Properties.Settings.Default.StatCurrentWinStreakAdv)
                    {
                        Properties.Settings.Default.StatWinStreakAdv = Properties.Settings.Default.StatCurrentWinStreakAdv;
                    }
                    else if (Properties.Settings.Default.StatLossStreakAdv < Properties.Settings.Default.StatCurrentLossStreakAdv)
                    {
                        Properties.Settings.Default.StatLossStreakAdv = Properties.Settings.Default.StatCurrentLossStreakAdv;
                    }

                    if (Properties.Settings.Default.StatGamesPlayedAdv > 0)
                    {
                        Properties.Settings.Default.StatWinPercentAdv =
                            Math.Round(((decimal)Properties.Settings.Default.StatGamesWonAdv /
                            (decimal)Properties.Settings.Default.StatGamesPlayedAdv) * 100, 2);
                    }

                    break;
            }
        }

        private static void CheckForWin(TileMap tileMap)
        {
            // Autocompletes the round if remaining tiles are Mines
            if ((tileMap.FlaggedMines == tileMap.Mines &&
                ((tileMap.Tiles.Count(tile => tile.State == TileState.Uncovered) + tileMap.Mines) == tileMap.Tiles.Count())) || (tileMap.Tiles.Count(tile => tile.State == TileState.Uncovered)) == tileMap.Tiles.Count() - tileMap.Mines)
            {
                // And we're done, notify the user
                if (Environment.OSVersion.Version.Major >= 6)
                {
                    tileMap.dispatcherTimer.Stop();
                    using (var enableThemingInScope = new EnableThemingInScope(true))
                    {
                        using (var taskDialog = new TaskDialog())
                        {
                            taskDialog.Caption = "Game Won";
                            taskDialog.InstructionText = "Congratulations, you won the game!";
                            taskDialog.Cancelable = true;
                            RecordStats(true);
                            compareTimes(tileMap.Seconds);
                            var windowInteropHelper = new WindowInteropHelper(App.Current.MainWindow);
                            taskDialog.OwnerWindowHandle = windowInteropHelper.Handle;
                            var closeDialog = new Action<object, TaskDialogResult>((s, tdr) =>
                            {
                                ((TaskDialog)((TaskDialogButton)s).HostingDialog).Close(tdr);
                            });
                            
                            var quit = new TaskDialogButton(
                                "ExitButton",
                                "Exit");
                            quit.Click += ((s, e) =>
                            {
                                closeDialog(s, TaskDialogResult.No);
                                App.Current.MainWindow.Close();
                            });

                            var playAgain = new TaskDialogButton(
                                "PlayAgainCommandLink",
                                "Play Again");
                            playAgain.Default = true;
                            playAgain.Click += ((s, e) =>
                            {
                                closeDialog(s, TaskDialogResult.Ok);
                                tileMap.NewGame();
                            });

                            taskDialog.Controls.Add(quit);
                            taskDialog.Controls.Add(playAgain);

                            if (taskDialog.Show() == TaskDialogResult.Cancel)
                            {
                                tileMap.NewGame();
                            }
                        }

                    }
                }
                else
                {
                    tileMap.dispatcherTimer.Stop();
                    // Provide a fallback implementation for windows XP
                    if (MessageBox.Show("Congratulations, you won the game!",
                        "Game Won",
                        MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes)
                    {
                        tileMap.NewGame();
                    }
                    else
                    {
                        App.Current.MainWindow.Close();
                    }
                }
            }

        }
        /// <summary>
        /// Resets all properties of Difficulty objects to 0, resets win/loss streak settings to 0
        /// </summary>
        /// <param name="data"></param>
        internal static void ResetStats(DifficultyManager data)
        {
            foreach (var difficulty in data.DifficultyList)
            {
                difficulty.Played = 0;
                difficulty.Wins = 0;
                difficulty.Current = 0;
                difficulty.WinPercent = 0;
                difficulty.WinStreak = 0;
                difficulty.LossStreak = 0;
                difficulty.Times.Clear();
                difficulty.ArrayTimes.Clear();
            }

            data.Save();
        }

        /// <summary>
        /// Compares the current winning game time to see if it is in the top 5 best times
        /// </summary>
        /// <param name="gameTime">the game time in seconds of the game just won</param>
        private static void compareTimes(int gameTime)
        {

            switch ((GameDifficulty)Properties.Settings.Default.GameOptionDifficultyInt)
            {
                case GameDifficulty.Beginner:
                    Properties.Settings.Default.StatBestTimesBeg.Add(gameTime);
                    Properties.Settings.Default.StatBestTimesBeg.Sort();
                    if (Properties.Settings.Default.StatBestTimesBeg.Count > 5)
                    {
                        Properties.Settings.Default.StatBestTimesBeg.RemoveAt(5);
                    }
                    break;
                case GameDifficulty.Intermediate:
                    Properties.Settings.Default.StatBestTimesInt.Add(gameTime);
                    Properties.Settings.Default.StatBestTimesInt.Sort();
                    if (Properties.Settings.Default.StatBestTimesInt.Count > 5)
                    {
                        Properties.Settings.Default.StatBestTimesInt.RemoveAt(5);
                    }
                    break;
                case GameDifficulty.Advanced:
                    Properties.Settings.Default.StatBestTimesAdv.Add(gameTime);
                    Properties.Settings.Default.StatBestTimesAdv.Sort();
                    if (Properties.Settings.Default.StatBestTimesAdv.Count > 5)
                    {
                        Properties.Settings.Default.StatBestTimesAdv.RemoveAt(5);
                    }
                    break;
            }
        }

    }
}
